{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Configurations for Colab"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "IN_COLAB = 'google.colab' in sys.modules\n",
    "\n",
    "if IN_COLAB:\n",
    "    !apt-get install -y xvfb python-opengl > /dev/null 2>&1\n",
    "    !pip install gym pyvirtualdisplay > /dev/null 2>&1\n",
    "    !pip install JSAnimation==0.1\n",
    "    \n",
    "    from pyvirtualdisplay import Display\n",
    "    \n",
    "    # Start virtual display\n",
    "    dis = Display(visible=0, size=(400, 400))\n",
    "    dis.start()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 08. Rainbow\n",
    "\n",
    "[M. Hessel et al., \"Rainbow: Combining Improvements in Deep Reinforcement Learning.\" arXiv preprint arXiv:1710.02298, 2017.](https://arxiv.org/pdf/1710.02298.pdf)\n",
    "\n",
    "We will integrate all the following seven components into a single integrated agent, which is called Rainbow!\n",
    "\n",
    "1. DQN\n",
    "2. Double DQN\n",
    "3. Prioritized Experience Replay\n",
    "4. Dueling Network\n",
    "5. Noisy Network\n",
    "6. Categorical DQN\n",
    "7. N-step Learning\n",
    "\n",
    "This method shows an impressive performance on the Atari 2600 benchmark, both in terms of data efficiency and final performance. \n",
    "\n",
    "![rainbow](https://user-images.githubusercontent.com/14961526/60591412-61748100-9dd9-11e9-84fb-076c7a61fbab.png)\n",
    "\n",
    "However, the integration is not so simple because some of components are not independent each other, so we will look into a number of points that people especailly feel confused.\n",
    "\n",
    "1. Noisy Network <-> Dueling Network\n",
    "2. Dueling Network <-> Categorical DQN\n",
    "3. Categorical DQN <-> Double DQN"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "import os\n",
    "import random\n",
    "from collections import deque\n",
    "from typing import Deque, Dict, List, Tuple\n",
    "\n",
    "import gym\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from IPython.display import clear_output\n",
    "from torch.nn.utils import clip_grad_norm_\n",
    "\n",
    "# download segment tree module\n",
    "if IN_COLAB:\n",
    "    !wget https://raw.githubusercontent.com/curt-park/rainbow-is-all-you-need/master/segment_tree.py\n",
    "\n",
    "from segment_tree import MinSegmentTree, SumSegmentTree"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Replay buffer\n",
    "\n",
    "Same as the basic N-step buffer. \n",
    "\n",
    "(Please see *01.dqn.ipynb*, *07.n_step_learning.ipynb* for detailed description about the basic (n-step) replay buffer.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ReplayBuffer:\n",
    "    \"\"\"A simple numpy replay buffer.\"\"\"\n",
    "\n",
    "    def __init__(\n",
    "        self, \n",
    "        obs_dim: int, \n",
    "        size: int, \n",
    "        batch_size: int = 32, \n",
    "        n_step: int = 1, \n",
    "        gamma: float = 0.99\n",
    "    ):\n",
    "        self.obs_buf = np.zeros([size, obs_dim], dtype=np.float32)\n",
    "        self.next_obs_buf = np.zeros([size, obs_dim], dtype=np.float32)\n",
    "        self.acts_buf = np.zeros([size], dtype=np.float32)\n",
    "        self.rews_buf = np.zeros([size], dtype=np.float32)\n",
    "        self.done_buf = np.zeros(size, dtype=np.float32)\n",
    "        self.max_size, self.batch_size = size, batch_size\n",
    "        self.ptr, self.size, = 0, 0\n",
    "        \n",
    "        # for N-step Learning\n",
    "        self.n_step_buffer = deque(maxlen=n_step)\n",
    "        self.n_step = n_step\n",
    "        self.gamma = gamma\n",
    "\n",
    "    def store(\n",
    "        self, \n",
    "        obs: np.ndarray, \n",
    "        act: np.ndarray, \n",
    "        rew: float, \n",
    "        next_obs: np.ndarray, \n",
    "        done: bool,\n",
    "    ) -> bool:\n",
    "        transition = (obs, act, rew, next_obs, done)\n",
    "        self.n_step_buffer.append(transition)\n",
    "\n",
    "        # single step transition is not ready\n",
    "        if len(self.n_step_buffer) < self.n_step:\n",
    "            return False\n",
    "        \n",
    "        # make a n-step transition\n",
    "        rew, next_obs, done = self._get_n_step_info(\n",
    "            self.n_step_buffer, self.gamma\n",
    "        )\n",
    "        obs, action = self.n_step_buffer[0][:2]\n",
    "        \n",
    "        self.obs_buf[self.ptr] = obs\n",
    "        self.next_obs_buf[self.ptr] = next_obs\n",
    "        self.acts_buf[self.ptr] = act\n",
    "        self.rews_buf[self.ptr] = rew\n",
    "        self.done_buf[self.ptr] = done\n",
    "        self.ptr = (self.ptr + 1) % self.max_size\n",
    "        self.size = min(self.size + 1, self.max_size)\n",
    "        \n",
    "        return True\n",
    "\n",
    "    def sample_batch(self) -> Dict[str, np.ndarray]:\n",
    "        idxs = np.random.choice(self.size, size=self.batch_size, replace=False)\n",
    "\n",
    "        return dict(\n",
    "            obs=self.obs_buf[idxs],\n",
    "            next_obs=self.next_obs_buf[idxs],\n",
    "            acts=self.acts_buf[idxs],\n",
    "            rews=self.rews_buf[idxs],\n",
    "            done=self.done_buf[idxs],\n",
    "            # for N-step Learning\n",
    "            indices=indices,\n",
    "        )\n",
    "    \n",
    "    def sample_batch_from_idxs(\n",
    "        self, idxs: np.ndarray\n",
    "    ) -> Dict[str, np.ndarray]:\n",
    "        # for N-step Learning\n",
    "        return dict(\n",
    "            obs=self.obs_buf[idxs],\n",
    "            next_obs=self.next_obs_buf[idxs],\n",
    "            acts=self.acts_buf[idxs],\n",
    "            rews=self.rews_buf[idxs],\n",
    "            done=self.done_buf[idxs],\n",
    "        )\n",
    "    \n",
    "    def _get_n_step_info(\n",
    "        self, n_step_buffer: Deque, gamma: float\n",
    "    ) -> Tuple[np.int64, np.ndarray, bool]:\n",
    "        \"\"\"Return n step rew, next_obs, and done.\"\"\"\n",
    "        # info of the last transition\n",
    "        rew, next_obs, done = n_step_buffer[-1][-3:]\n",
    "\n",
    "        for transition in reversed(list(n_step_buffer)[:-1]):\n",
    "            r, n_o, d = transition[-3:]\n",
    "\n",
    "            rew = r + gamma * rew * (1 - d)\n",
    "            next_obs, done = (n_o, d) if d else (next_obs, done)\n",
    "\n",
    "        return rew, next_obs, done\n",
    "\n",
    "    def __len__(self) -> int:\n",
    "        return self.size"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Prioritized replay Buffer\n",
    "\n",
    "`store` method returns boolean in order to inform if a N-step transition has been generated.\n",
    "\n",
    "(Please see *02.per.ipynb* for detailed description about PER.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class PrioritizedReplayBuffer(ReplayBuffer):\n",
    "    \"\"\"Prioritized Replay buffer.\n",
    "    \n",
    "    Attributes:\n",
    "        max_priority (float): max priority\n",
    "        tree_ptr (int): next index of tree\n",
    "        alpha (float): alpha parameter for prioritized replay buffer\n",
    "        sum_tree (SumSegmentTree): sum tree for prior\n",
    "        min_tree (MinSegmentTree): min tree for min prior to get max weight\n",
    "        \n",
    "    \"\"\"\n",
    "    \n",
    "    def __init__(\n",
    "        self, \n",
    "        obs_dim: int, \n",
    "        size: int, \n",
    "        batch_size: int = 32, \n",
    "        alpha: float = 0.6,\n",
    "    ):\n",
    "        \"\"\"Initialization.\"\"\"\n",
    "        assert alpha >= 0\n",
    "        \n",
    "        super(PrioritizedReplayBuffer, self).__init__(obs_dim, size, batch_size)\n",
    "        self.max_priority, self.tree_ptr = 1.0, 0\n",
    "        self.alpha = alpha\n",
    "        \n",
    "        # capacity must be positive and a power of 2.\n",
    "        tree_capacity = 1\n",
    "        while tree_capacity < self.max_size:\n",
    "            tree_capacity *= 2\n",
    "\n",
    "        self.sum_tree = SumSegmentTree(tree_capacity)\n",
    "        self.min_tree = MinSegmentTree(tree_capacity)\n",
    "        \n",
    "    def store(\n",
    "        self, \n",
    "        obs: np.ndarray, \n",
    "        act: int, \n",
    "        rew: float, \n",
    "        next_obs: np.ndarray, \n",
    "        done: bool,\n",
    "    ) -> bool:\n",
    "        \"\"\"Store experience and priority.\"\"\"\n",
    "        transition = super().store(obs, act, rew, next_obs, done)\n",
    "        \n",
    "        self.sum_tree[self.tree_ptr] = self.max_priority ** self.alpha\n",
    "        self.min_tree[self.tree_ptr] = self.max_priority ** self.alpha\n",
    "        self.tree_ptr = (self.tree_ptr + 1) % self.max_size\n",
    "        \n",
    "        return transition\n",
    "\n",
    "    def sample_batch(self, beta: float = 0.4) -> Dict[str, np.ndarray]:\n",
    "        \"\"\"Sample a batch of experiences.\"\"\"\n",
    "        assert len(self) >= self.batch_size\n",
    "        assert beta > 0\n",
    "        \n",
    "        indices = self._sample_proportional()\n",
    "        \n",
    "        obs = self.obs_buf[indices]\n",
    "        next_obs = self.next_obs_buf[indices]\n",
    "        acts = self.acts_buf[indices]\n",
    "        rews = self.rews_buf[indices]\n",
    "        done = self.done_buf[indices]\n",
    "        weights = np.array([self._calculate_weight(i, beta) for i in indices])\n",
    "        \n",
    "        return dict(\n",
    "            obs=obs,\n",
    "            next_obs=next_obs,\n",
    "            acts=acts,\n",
    "            rews=rews,\n",
    "            done=done,\n",
    "            weights=weights,\n",
    "            indices=indices,\n",
    "        )\n",
    "        \n",
    "    def update_priorities(self, indices: List[int], priorities: np.ndarray):\n",
    "        \"\"\"Update priorities of sampled transitions.\"\"\"\n",
    "        assert len(indices) == len(priorities)\n",
    "\n",
    "        for idx, priority in zip(indices, priorities):\n",
    "            assert priority > 0\n",
    "            assert 0 <= idx < len(self)\n",
    "\n",
    "            self.sum_tree[idx] = priority ** self.alpha\n",
    "            self.min_tree[idx] = priority ** self.alpha\n",
    "\n",
    "            self.max_priority = max(self.max_priority, priority)\n",
    "            \n",
    "    def _sample_proportional(self) -> List[int]:\n",
    "        \"\"\"Sample indices based on proportions.\"\"\"\n",
    "        indices = []\n",
    "        p_total = self.sum_tree.sum(0, len(self) - 1)\n",
    "        segment = p_total / self.batch_size\n",
    "        \n",
    "        for i in range(self.batch_size):\n",
    "            a = segment * i\n",
    "            b = segment * (i + 1)\n",
    "            upperbound = random.uniform(a, b)\n",
    "            idx = self.sum_tree.retrieve(upperbound)\n",
    "            indices.append(idx)\n",
    "            \n",
    "        return indices\n",
    "    \n",
    "    def _calculate_weight(self, idx: int, beta: float):\n",
    "        \"\"\"Calculate the weight of the experience at idx.\"\"\"\n",
    "        # get max weight\n",
    "        p_min = self.min_tree.min() / self.sum_tree.sum()\n",
    "        max_weight = (p_min * len(self)) ** (-beta)\n",
    "        \n",
    "        # calculate weights\n",
    "        p_sample = self.sum_tree[idx] / self.sum_tree.sum()\n",
    "        weight = (p_sample * len(self)) ** (-beta)\n",
    "        weight = weight / max_weight\n",
    "        \n",
    "        return weight"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Noisy Layer\n",
    "\n",
    "Please see *05.noisy_net.ipynb* for detailed description.\n",
    "\n",
    "**References:**\n",
    "\n",
    "- https://github.com/higgsfield/RL-Adventure/blob/master/5.noisy%20dqn.ipynb\n",
    "- https://github.com/Kaixhin/Rainbow/blob/master/model.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class NoisyLinear(nn.Module):\n",
    "    \"\"\"Noisy linear module for NoisyNet.\n",
    "    \n",
    "    \n",
    "        \n",
    "    Attributes:\n",
    "        in_features (int): input size of linear module\n",
    "        out_features (int): output size of linear module\n",
    "        std_init (float): initial std value\n",
    "        weight_mu (nn.Parameter): mean value weight parameter\n",
    "        weight_sigma (nn.Parameter): std value weight parameter\n",
    "        bias_mu (nn.Parameter): mean value bias parameter\n",
    "        bias_sigma (nn.Parameter): std value bias parameter\n",
    "        \n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(\n",
    "        self, \n",
    "        in_features: int, \n",
    "        out_features: int, \n",
    "        std_init: float = 0.5,\n",
    "    ):\n",
    "        \"\"\"Initialization.\"\"\"\n",
    "        super(NoisyLinear, self).__init__()\n",
    "        \n",
    "        self.in_features = in_features\n",
    "        self.out_features = out_features\n",
    "        self.std_init = std_init\n",
    "\n",
    "        self.weight_mu = nn.Parameter(torch.Tensor(out_features, in_features))\n",
    "        self.weight_sigma = nn.Parameter(\n",
    "            torch.Tensor(out_features, in_features)\n",
    "        )\n",
    "        self.register_buffer(\n",
    "            \"weight_epsilon\", torch.Tensor(out_features, in_features)\n",
    "        )\n",
    "\n",
    "        self.bias_mu = nn.Parameter(torch.Tensor(out_features))\n",
    "        self.bias_sigma = nn.Parameter(torch.Tensor(out_features))\n",
    "        self.register_buffer(\"bias_epsilon\", torch.Tensor(out_features))\n",
    "\n",
    "        self.reset_parameters()\n",
    "        self.reset_noise()\n",
    "\n",
    "    def reset_parameters(self):\n",
    "        \"\"\"Reset trainable network parameters (factorized gaussian noise).\"\"\"\n",
    "        mu_range = 1 / math.sqrt(self.in_features)\n",
    "        self.weight_mu.data.uniform_(-mu_range, mu_range)\n",
    "        self.weight_sigma.data.fill_(\n",
    "            self.std_init / math.sqrt(self.in_features)\n",
    "        )\n",
    "        self.bias_mu.data.uniform_(-mu_range, mu_range)\n",
    "        self.bias_sigma.data.fill_(\n",
    "            self.std_init / math.sqrt(self.out_features)\n",
    "        )\n",
    "\n",
    "    def reset_noise(self):\n",
    "        \"\"\"Make new noise.\"\"\"\n",
    "        epsilon_in = self.scale_noise(self.in_features)\n",
    "        epsilon_out = self.scale_noise(self.out_features)\n",
    "\n",
    "        # outer product\n",
    "        self.weight_epsilon.copy_(epsilon_out.ger(epsilon_in))\n",
    "        self.bias_epsilon.copy_(epsilon_out)\n",
    "\n",
    "    def forward(self, x: torch.Tensor) -> torch.Tensor:\n",
    "        \"\"\"Forward method implementation.\n",
    "        \n",
    "        We don't use separate statements on train / eval mode.\n",
    "        It doesn't show remarkable difference of performance.\n",
    "        \"\"\"\n",
    "        return F.linear(\n",
    "            x,\n",
    "            self.weight_mu + self.weight_sigma * self.weight_epsilon,\n",
    "            self.bias_mu + self.bias_sigma * self.bias_epsilon,\n",
    "        )\n",
    "    \n",
    "    @staticmethod\n",
    "    def scale_noise(size: int) -> torch.Tensor:\n",
    "        \"\"\"Set scale to make noise (factorized gaussian noise).\"\"\"\n",
    "        x = torch.FloatTensor(np.random.normal(loc=0.0, scale=1.0, size=size))\n",
    "\n",
    "        return x.sign().mul(x.abs().sqrt())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## NoisyNet + DuelingNet + Categorical DQN\n",
    "\n",
    "#### NoisyNet + DuelingNet\n",
    "\n",
    "NoisyLinear is employed for the last two layers of advantage and value layers. The noise should be reset at evey update step.\n",
    "\n",
    "#### DuelingNet + Categorical DQN\n",
    "\n",
    "The dueling network architecture is adapted for use with return distributions. The network has a shared representation, which is then fed into a value stream with atom_size outputs, and into an advantage stream with atom_size × out_dim outputs. For each atom, the value and advantage streams are aggregated, as in dueling DQN, and then passed through a softmax layer to obtain the normalized parametric distributions used to estimate the returns’ distributions.\n",
    "\n",
    "```\n",
    "        advantage = self.advantage_layer(adv_hid).view(-1, self.out_dim, self.atom_size)\n",
    "        value = self.value_layer(val_hid).view(-1, 1, self.atom_size)\n",
    "        q_atoms = value + advantage - advantage.mean(dim=1, keepdim=True)\n",
    "        \n",
    "        dist = F.softmax(q_atoms, dim=-1)\n",
    "```\n",
    "\n",
    "(Please see *04.dueling.ipynb*, *05.noisy_net.ipynb*, *06.categorical_dqn.ipynb* for detailed description of each component's network architecture.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Network(nn.Module):\n",
    "    def __init__(\n",
    "        self, \n",
    "        in_dim: int, \n",
    "        out_dim: int, \n",
    "        atom_size: int, \n",
    "        support: torch.Tensor\n",
    "    ):\n",
    "        \"\"\"Initialization.\"\"\"\n",
    "        super(Network, self).__init__()\n",
    "        \n",
    "        self.support = support\n",
    "        self.out_dim = out_dim\n",
    "        self.atom_size = atom_size\n",
    "\n",
    "        # set common feature layer\n",
    "        self.feature_layer = nn.Sequential(\n",
    "            nn.Linear(in_dim, 128), \n",
    "            nn.ReLU(),\n",
    "        )\n",
    "        \n",
    "        # set advantage layer\n",
    "        self.advantage_hidden_layer = NoisyLinear(128, 128)\n",
    "        self.advantage_layer = NoisyLinear(128, out_dim * atom_size)\n",
    "\n",
    "        # set value layer\n",
    "        self.value_hidden_layer = NoisyLinear(128, 128)\n",
    "        self.value_layer = NoisyLinear(128, atom_size)\n",
    "\n",
    "    def forward(self, x: torch.Tensor) -> torch.Tensor:\n",
    "        \"\"\"Forward method implementation.\"\"\"\n",
    "        dist = self.dist(x)\n",
    "        q = torch.sum(dist * self.support, dim=2)\n",
    "        \n",
    "        return q\n",
    "    \n",
    "    def dist(self, x: torch.Tensor) -> torch.Tensor:\n",
    "        \"\"\"Get distribution for atoms.\"\"\"\n",
    "        feature = self.feature_layer(x)\n",
    "        adv_hid = F.relu(self.advantage_hidden_layer(feature))\n",
    "        val_hid = F.relu(self.value_hidden_layer(feature))\n",
    "        \n",
    "        advantage = self.advantage_layer(adv_hid).view(\n",
    "            -1, self.out_dim, self.atom_size\n",
    "        )\n",
    "        value = self.value_layer(val_hid).view(-1, 1, self.atom_size)\n",
    "        q_atoms = value + advantage - advantage.mean(dim=1, keepdim=True)\n",
    "        \n",
    "        dist = F.softmax(q_atoms, dim=-1)\n",
    "        \n",
    "        return dist\n",
    "    \n",
    "    def reset_noise(self):\n",
    "        \"\"\"Reset all noisy layers.\"\"\"\n",
    "        self.advantage_hidden_layer.reset_noise()\n",
    "        self.advantage_layer.reset_noise()\n",
    "        self.value_hidden_layer.reset_noise()\n",
    "        self.value_layer.reset_noise()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Rainbow Agent\n",
    "\n",
    "Here is a summary of DQNAgent class.\n",
    "\n",
    "| Method           | Note                                                 |\n",
    "| ---              | ---                                                  |\n",
    "|select_action     | select an action from the input state.               |\n",
    "|step              | take an action and return the response of the env.   |\n",
    "|compute_dqn_loss  | return dqn loss.                                     |\n",
    "|update_model      | update the model by gradient descent.                |\n",
    "|target_hard_update| hard update from the local model to the target model.|\n",
    "|train             | train the agent during num_frames.                   |\n",
    "|test              | test the agent (1 episode).                          |\n",
    "|plot              | plot the training progresses.                        |\n",
    "\n",
    "#### Categorical DQN + Double DQN\n",
    "\n",
    "The idea of Double Q-learning is to reduce overestimations by decomposing the max operation in the target into action selection and action evaluation. Here, we use `self.dqn` instead of `self.dqn_target` to obtain the target actions.\n",
    "\n",
    "```\n",
    "        # Categorical DQN + Double DQN\n",
    "        # target_dqn is used when we don't employ double DQN\n",
    "        next_action = self.dqn(next_state).argmax(1)\n",
    "        next_dist = self.dqn_target.dist(next_state)\n",
    "        next_dist = next_dist[range(self.batch_size), next_action]\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DQNAgent:\n",
    "    \"\"\"DQN Agent interacting with environment.\n",
    "    \n",
    "    Attribute:\n",
    "        env (gym.Env): openAI Gym environment\n",
    "        memory (ReplayBuffer): replay memory to store transitions\n",
    "        batch_size (int): batch size for sampling\n",
    "        target_update (int): period for target model's hard update\n",
    "        gamma (float): discount factor\n",
    "        dqn (Network): model to train and select actions\n",
    "        dqn_target (Network): target model to update\n",
    "        optimizer (torch.optim): optimizer for training dqn\n",
    "        transition (list): transition information including \n",
    "                           state, action, reward, next_state, done\n",
    "        v_min (float): min value of support\n",
    "        v_max (float): max value of support\n",
    "        atom_size (int): the unit number of support\n",
    "        support (torch.Tensor): support for categorical dqn\n",
    "        use_n_step (bool): whether to use n_step memory\n",
    "        n_step (int): step number to calculate n-step td error\n",
    "        memory_n (ReplayBuffer): n-step replay buffer\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(\n",
    "        self, \n",
    "        env: gym.Env,\n",
    "        memory_size: int,\n",
    "        batch_size: int,\n",
    "        target_update: int,\n",
    "        gamma: float = 0.99,\n",
    "        # PER parameters\n",
    "        alpha: float = 0.6,\n",
    "        beta: float = 0.4,\n",
    "        prior_eps: float = 1e-6,\n",
    "        # Categorical DQN parameters\n",
    "        v_min: float = 0.0,\n",
    "        v_max: float = 200.0,\n",
    "        atom_size: int = 51,\n",
    "        # N-step Learning\n",
    "        n_step: int = 3,\n",
    "    ):\n",
    "        \"\"\"Initialization.\n",
    "        \n",
    "        Args:\n",
    "            env (gym.Env): openAI Gym environment\n",
    "            memory_size (int): length of memory\n",
    "            batch_size (int): batch size for sampling\n",
    "            target_update (int): period for target model's hard update\n",
    "            lr (float): learning rate\n",
    "            gamma (float): discount factor\n",
    "            alpha (float): determines how much prioritization is used\n",
    "            beta (float): determines how much importance sampling is used\n",
    "            prior_eps (float): guarantees every transition can be sampled\n",
    "            v_min (float): min value of support\n",
    "            v_max (float): max value of support\n",
    "            atom_size (int): the unit number of support\n",
    "            n_step (int): step number to calculate n-step td error\n",
    "        \"\"\"\n",
    "        obs_dim = env.observation_space.shape[0]\n",
    "        action_dim = env.action_space.n\n",
    "        \n",
    "        self.env = env\n",
    "        self.memory = ReplayBuffer(obs_dim, memory_size, batch_size)\n",
    "        self.batch_size = batch_size\n",
    "        self.target_update = target_update\n",
    "        self.gamma = gamma\n",
    "        # NoisyNet: All attributes related to epsilon are removed\n",
    "        \n",
    "        # device: cpu / gpu\n",
    "        self.device = torch.device(\n",
    "            \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
    "        )\n",
    "        print(self.device)\n",
    "        \n",
    "        # PER\n",
    "        # memory for 1-step Learning\n",
    "        self.beta = beta\n",
    "        self.prior_eps = prior_eps\n",
    "        self.memory = PrioritizedReplayBuffer(\n",
    "            obs_dim, memory_size, batch_size, alpha=alpha\n",
    "        )\n",
    "        \n",
    "        # memory for N-step Learning\n",
    "        self.use_n_step = True if n_step > 1 else False\n",
    "        if self.use_n_step:\n",
    "            self.n_step = n_step\n",
    "            self.memory_n = ReplayBuffer(\n",
    "                obs_dim, memory_size, batch_size, n_step=n_step, gamma=gamma\n",
    "            )\n",
    "            \n",
    "        # Categorical DQN parameters\n",
    "        self.v_min = v_min\n",
    "        self.v_max = v_max\n",
    "        self.atom_size = atom_size\n",
    "        self.support = torch.linspace(\n",
    "            self.v_min, self.v_max, self.atom_size\n",
    "        ).to(self.device)\n",
    "\n",
    "        # networks: dqn, dqn_target\n",
    "        self.dqn = Network(\n",
    "            obs_dim, action_dim, self.atom_size, self.support\n",
    "        ).to(self.device)\n",
    "        self.dqn_target = Network(\n",
    "            obs_dim, action_dim, self.atom_size, self.support\n",
    "        ).to(self.device)\n",
    "        self.dqn_target.load_state_dict(self.dqn.state_dict())\n",
    "        self.dqn_target.eval()\n",
    "        \n",
    "        # optimizer\n",
    "        self.optimizer = optim.Adam(self.dqn.parameters())\n",
    "\n",
    "        # transition to store in memory\n",
    "        self.transition = list()\n",
    "        \n",
    "        # mode: train / test\n",
    "        self.is_test = False\n",
    "\n",
    "    def select_action(self, state: np.ndarray) -> np.ndarray:\n",
    "        \"\"\"Select an action from the input state.\"\"\"\n",
    "        # NoisyNet: no epsilon greedy action selection\n",
    "        selected_action = self.dqn(\n",
    "            torch.FloatTensor(state).to(self.device)\n",
    "        ).argmax()\n",
    "        selected_action = selected_action.detach().cpu().numpy()\n",
    "        \n",
    "        if not self.is_test:\n",
    "            self.transition = [state, selected_action]\n",
    "        \n",
    "        return selected_action\n",
    "\n",
    "    def step(self, action: np.ndarray) -> Tuple[np.ndarray, np.float64, bool]:\n",
    "        \"\"\"Take an action and return the response of the env.\"\"\"\n",
    "        next_state, reward, done, _ = self.env.step(action)\n",
    "\n",
    "        if not self.is_test:\n",
    "            self.transition += [reward, next_state, done]\n",
    "            \n",
    "            # add N-step transition\n",
    "            if self.use_n_step:\n",
    "                is_n_step_stored = self.memory_n.store(*self.transition)\n",
    "\n",
    "            # add a single step transition\n",
    "            if not self.use_n_step or is_n_step_stored:\n",
    "                self.memory.store(*self.transition)\n",
    "    \n",
    "        return next_state, reward, done\n",
    "\n",
    "    def update_model(self) -> torch.Tensor:\n",
    "        \"\"\"Update the model by gradient descent.\"\"\"\n",
    "        # PER needs beta to calculate weights\n",
    "        samples = self.memory.sample_batch(self.beta)\n",
    "        weights = torch.FloatTensor(\n",
    "            samples[\"weights\"].reshape(-1, 1)\n",
    "        ).to(self.device)\n",
    "        indices = samples[\"indices\"]\n",
    "        \n",
    "        # 1-step Learning loss\n",
    "        elementwise_loss = self._compute_dqn_loss(samples, self.gamma)\n",
    "        \n",
    "        # PER: importance sampling before average\n",
    "        loss = torch.mean(elementwise_loss * weights)\n",
    "        \n",
    "        # N-step Learning loss\n",
    "        # we are gonna combine 1-step loss and n-step loss so as to\n",
    "        # prevent high-variance. The original rainbow employs n-step loss only.\n",
    "        if self.use_n_step:\n",
    "            gamma = self.gamma ** self.n_step\n",
    "            samples = self.memory_n.sample_batch_from_idxs(indices)\n",
    "            elementwise_loss_n_loss = self._compute_dqn_loss(samples, gamma)\n",
    "            elementwise_loss += elementwise_loss_n_loss\n",
    "            \n",
    "            # PER: importance sampling before average\n",
    "            loss = torch.mean(elementwise_loss * weights)\n",
    "\n",
    "        self.optimizer.zero_grad()\n",
    "        loss.backward()\n",
    "        # gradient clipping\n",
    "        # https://pytorch.org/docs/stable/nn.html#torch.nn.utils.clip_grad_norm_\n",
    "        clip_grad_norm_(self.dqn.parameters(), 1.0, norm_type=1)\n",
    "        self.optimizer.step()\n",
    "        \n",
    "        # PER: update priorities\n",
    "        loss_for_prior = elementwise_loss.detach().cpu().numpy()\n",
    "        new_priorities = loss_for_prior + self.prior_eps\n",
    "        self.memory.update_priorities(indices, new_priorities)\n",
    "        \n",
    "        # NoisyNet: reset noise\n",
    "        self.dqn.reset_noise()\n",
    "        self.dqn_target.reset_noise()\n",
    "\n",
    "        return loss.item()\n",
    "        \n",
    "    def train(self, num_frames: int, plotting_interval: int = 200):\n",
    "        \"\"\"Train the agent.\"\"\"\n",
    "        self.is_test = False\n",
    "        \n",
    "        state = self.env.reset()\n",
    "        update_cnt = 0\n",
    "        losses = []\n",
    "        scores = []\n",
    "        score = 0\n",
    "\n",
    "        for frame_idx in range(1, num_frames + 1):\n",
    "            action = self.select_action(state)\n",
    "            next_state, reward, done = self.step(action)\n",
    "\n",
    "            state = next_state\n",
    "            score += reward\n",
    "            \n",
    "            # NoisyNet: removed decrease of epsilon\n",
    "            \n",
    "            # PER: increase beta\n",
    "            fraction = min(frame_idx / num_frames, 1.0)\n",
    "            self.beta = self.beta + fraction * (1.0 - self.beta)\n",
    "\n",
    "            # if episode ends\n",
    "            if done:\n",
    "                state = env.reset()\n",
    "                scores.append(score)\n",
    "                score = 0\n",
    "\n",
    "            # if training is ready\n",
    "            if len(self.memory) >= self.batch_size:\n",
    "                loss = self.update_model()\n",
    "                losses.append(loss)\n",
    "                update_cnt += 1\n",
    "                \n",
    "                # if hard update is needed\n",
    "                if update_cnt % self.target_update == 0:\n",
    "                    self._target_hard_update()\n",
    "\n",
    "            # plotting\n",
    "            if frame_idx % plotting_interval == 0:\n",
    "                self._plot(frame_idx, scores, losses)\n",
    "                \n",
    "        self.env.close()\n",
    "                \n",
    "    def test(self) -> List[np.ndarray]:\n",
    "        \"\"\"Test the agent.\"\"\"\n",
    "        self.is_test = True\n",
    "        \n",
    "        state = self.env.reset()\n",
    "        done = False\n",
    "        score = 0\n",
    "        \n",
    "        frames = []\n",
    "        while not done:\n",
    "            frames.append(self.env.render(mode=\"rgb_array\"))\n",
    "            action = self.select_action(state)\n",
    "            next_state, reward, done = self.step(action)\n",
    "\n",
    "            state = next_state\n",
    "            score += reward\n",
    "        \n",
    "        print(\"score: \", score)\n",
    "        self.env.close()\n",
    "        \n",
    "        return frames\n",
    "\n",
    "    def _compute_dqn_loss(self, samples: Dict[str, np.ndarray], gamma: float) -> torch.Tensor:\n",
    "        \"\"\"Return categorical dqn loss.\"\"\"\n",
    "        device = self.device  # for shortening the following lines\n",
    "        state = torch.FloatTensor(samples[\"obs\"]).to(device)\n",
    "        next_state = torch.FloatTensor(samples[\"next_obs\"]).to(device)\n",
    "        action = torch.LongTensor(samples[\"acts\"]).to(device)\n",
    "        reward = torch.FloatTensor(samples[\"rews\"].reshape(-1, 1)).to(device)\n",
    "        done = torch.FloatTensor(samples[\"done\"].reshape(-1, 1)).to(device)\n",
    "        \n",
    "        # Categorical DQN algorithm\n",
    "        delta_z = float(self.v_max - self.v_min) / (self.atom_size - 1)\n",
    "\n",
    "        with torch.no_grad():\n",
    "            # Double DQN\n",
    "            next_action = self.dqn(next_state).argmax(1)\n",
    "            next_dist = self.dqn_target.dist(next_state)\n",
    "            next_dist = next_dist[range(self.batch_size), next_action]\n",
    "\n",
    "            t_z = reward + (1 - done) * gamma * self.support\n",
    "            t_z = t_z.clamp(min=self.v_min, max=self.v_max)\n",
    "            b = (t_z - self.v_min) / delta_z\n",
    "            l = b.floor().long()\n",
    "            u = b.ceil().long()\n",
    "\n",
    "            offset = (\n",
    "                torch.linspace(\n",
    "                    0, (batch_size - 1) * self.atom_size, self.batch_size\n",
    "                ).long()\n",
    "                .unsqueeze(1)\n",
    "                .expand(self.batch_size, self.atom_size)\n",
    "                .to(self.device)\n",
    "            )\n",
    "\n",
    "            proj_dist = torch.zeros(next_dist.size(), device=self.device)\n",
    "            proj_dist.view(-1).index_add_(\n",
    "                0, (l + offset).view(-1), (next_dist * (u.float() - b)).view(-1)\n",
    "            )\n",
    "            proj_dist.view(-1).index_add_(\n",
    "                0, (u + offset).view(-1), (next_dist * (b - l.float())).view(-1)\n",
    "            )\n",
    "\n",
    "        dist = self.dqn.dist(state)\n",
    "        log_p = torch.log(dist[range(self.batch_size), action])\n",
    "        elementwise_loss = -(proj_dist * log_p).sum(1)\n",
    "\n",
    "        return elementwise_loss\n",
    "\n",
    "    def _target_hard_update(self):\n",
    "        \"\"\"Hard update: target <- local.\"\"\"\n",
    "        self.dqn_target.load_state_dict(self.dqn.state_dict())\n",
    "                \n",
    "    def _plot(\n",
    "        self, \n",
    "        frame_idx: int, \n",
    "        scores: List[float], \n",
    "        losses: List[float],\n",
    "    ):\n",
    "        \"\"\"Plot the training progresses.\"\"\"\n",
    "        clear_output(True)\n",
    "        plt.figure(figsize=(20, 5))\n",
    "        plt.subplot(131)\n",
    "        plt.title('frame %s. score: %s' % (frame_idx, np.mean(scores[-10:])))\n",
    "        plt.plot(scores)\n",
    "        plt.subplot(132)\n",
    "        plt.title('loss')\n",
    "        plt.plot(losses)\n",
    "        plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Environment\n",
    "\n",
    "You can see the [code](https://github.com/openai/gym/blob/master/gym/envs/classic_control/cartpole.py) and [configurations](https://github.com/openai/gym/blob/master/gym/envs/__init__.py#L53) of CartPole-v0 from OpenAI's repository."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# environment\n",
    "env_id = \"CartPole-v0\"\n",
    "env = gym.make(env_id)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Set random seed"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[777]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "seed = 777\n",
    "\n",
    "def seed_torch(seed):\n",
    "    torch.manual_seed(seed)\n",
    "    if torch.backends.cudnn.enabled:\n",
    "        torch.backends.cudnn.benchmark = False\n",
    "        torch.backends.cudnn.deterministic = True\n",
    "\n",
    "np.random.seed(seed)\n",
    "random.seed(seed)\n",
    "seed_torch(seed)\n",
    "env.seed(seed)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Initialize"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cuda\n"
     ]
    }
   ],
   "source": [
    "# parameters\n",
    "num_frames = 10000\n",
    "memory_size = 1000\n",
    "batch_size = 32\n",
    "target_update = 200\n",
    "\n",
    "# train\n",
    "agent = DQNAgent(env, memory_size, batch_size, target_update)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Train"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 1440x360 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "agent.train(num_frames)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Test\n",
    "\n",
    "Run the trained agent (1 episode)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "score:  200.0\n"
     ]
    }
   ],
   "source": [
    "frames = agent.test()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Render"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<script language=\"javascript\">\n",
       "  /* Define the Animation class */\n",
       "  function Animation(frames, img_id, slider_id, interval, loop_select_id){\n",
       "    this.img_id = img_id;\n",
       "    this.slider_id = slider_id;\n",
       "    this.loop_select_id = loop_select_id;\n",
       "    this.interval = interval;\n",
       "    this.current_frame = 0;\n",
       "    this.direction = 0;\n",
       "    this.timer = null;\n",
       "    this.frames = new Array(frames.length);\n",
       "\n",
       "    for (var i=0; i<frames.length; i++)\n",
       "    {\n",
       "     this.frames[i] = new Image();\n",
       "     this.frames[i].src = frames[i];\n",
       "    }\n",
       "    document.getElementById(this.slider_id).max = this.frames.length - 1;\n",
       "    this.set_frame(this.current_frame);\n",
       "  }\n",
       "\n",
       "  Animation.prototype.get_loop_state = function(){\n",
       "    var button_group = document[this.loop_select_id].state;\n",
       "    for (var i = 0; i < button_group.length; i++) {\n",
       "        var button = button_group[i];\n",
       "        if (button.checked) {\n",
       "            return button.value;\n",
       "        }\n",
       "    }\n",
       "    return undefined;\n",
       "  }\n",
       "\n",
       "  Animation.prototype.set_frame = function(frame){\n",
       "    this.current_frame = frame;\n",
       "    document.getElementById(this.img_id).src = this.frames[this.current_frame].src;\n",
       "    document.getElementById(this.slider_id).value = this.current_frame;\n",
       "  }\n",
       "\n",
       "  Animation.prototype.next_frame = function()\n",
       "  {\n",
       "    this.set_frame(Math.min(this.frames.length - 1, this.current_frame + 1));\n",
       "  }\n",
       "\n",
       "  Animation.prototype.previous_frame = function()\n",
       "  {\n",
       "    this.set_frame(Math.max(0, this.current_frame - 1));\n",
       "  }\n",
       "\n",
       "  Animation.prototype.first_frame = function()\n",
       "  {\n",
       "    this.set_frame(0);\n",
       "  }\n",
       "\n",
       "  Animation.prototype.last_frame = function()\n",
       "  {\n",
       "    this.set_frame(this.frames.length - 1);\n",
       "  }\n",
       "\n",
       "  Animation.prototype.slower = function()\n",
       "  {\n",
       "    this.interval /= 0.7;\n",
       "    if(this.direction > 0){this.play_animation();}\n",
       "    else if(this.direction < 0){this.reverse_animation();}\n",
       "  }\n",
       "\n",
       "  Animation.prototype.faster = function()\n",
       "  {\n",
       "    this.interval *= 0.7;\n",
       "    if(this.direction > 0){this.play_animation();}\n",
       "    else if(this.direction < 0){this.reverse_animation();}\n",
       "  }\n",
       "\n",
       "  Animation.prototype.anim_step_forward = function()\n",
       "  {\n",
       "    this.current_frame += 1;\n",
       "    if(this.current_frame < this.frames.length){\n",
       "      this.set_frame(this.current_frame);\n",
       "    }else{\n",
       "      var loop_state = this.get_loop_state();\n",
       "      if(loop_state == \"loop\"){\n",
       "        this.first_frame();\n",
       "      }else if(loop_state == \"reflect\"){\n",
       "        this.last_frame();\n",
       "        this.reverse_animation();\n",
       "      }else{\n",
       "        this.pause_animation();\n",
       "        this.last_frame();\n",
       "      }\n",
       "    }\n",
       "  }\n",
       "\n",
       "  Animation.prototype.anim_step_reverse = function()\n",
       "  {\n",
       "    this.current_frame -= 1;\n",
       "    if(this.current_frame >= 0){\n",
       "      this.set_frame(this.current_frame);\n",
       "    }else{\n",
       "      var loop_state = this.get_loop_state();\n",
       "      if(loop_state == \"loop\"){\n",
       "        this.last_frame();\n",
       "      }else if(loop_state == \"reflect\"){\n",
       "        this.first_frame();\n",
       "        this.play_animation();\n",
       "      }else{\n",
       "        this.pause_animation();\n",
       "        this.first_frame();\n",
       "      }\n",
       "    }\n",
       "  }\n",
       "\n",
       "  Animation.prototype.pause_animation = function()\n",
       "  {\n",
       "    this.direction = 0;\n",
       "    if (this.timer){\n",
       "      clearInterval(this.timer);\n",
       "      this.timer = null;\n",
       "    }\n",
       "  }\n",
       "\n",
       "  Animation.prototype.play_animation = function()\n",
       "  {\n",
       "    this.pause_animation();\n",
       "    this.direction = 1;\n",
       "    var t = this;\n",
       "    if (!this.timer) this.timer = setInterval(function(){t.anim_step_forward();}, this.interval);\n",
       "  }\n",
       "\n",
       "  Animation.prototype.reverse_animation = function()\n",
       "  {\n",
       "    this.pause_animation();\n",
       "    this.direction = -1;\n",
       "    var t = this;\n",
       "    if (!this.timer) this.timer = setInterval(function(){t.anim_step_reverse();}, this.interval);\n",
       "  }\n",
       "</script>\n",
       "\n",
       "<div class=\"animation\" align=\"center\">\n",
       "    <img id=\"_anim_imgICDEEVXJXWHQDUXN\">\n",
       "    <br>\n",
       "    <input id=\"_anim_sliderICDEEVXJXWHQDUXN\" type=\"range\" style=\"width:350px\" name=\"points\" min=\"0\" max=\"1\" step=\"1\" value=\"0\" onchange=\"animICDEEVXJXWHQDUXN.set_frame(parseInt(this.value));\"></input>\n",
       "    <br>\n",
       "    <button onclick=\"animICDEEVXJXWHQDUXN.slower()\">&#8211;</button>\n",
       "    <button onclick=\"animICDEEVXJXWHQDUXN.first_frame()\"><img class=\"anim_icon\" src=\"\"></button>\n",
       "    <button onclick=\"animICDEEVXJXWHQDUXN.previous_frame()\"><img class=\"anim_icon\" src=\"\"></button>\n",
       "    <button onclick=\"animICDEEVXJXWHQDUXN.reverse_animation()\"><img class=\"anim_icon\" src=\"\"></button>\n",
       "    <button onclick=\"animICDEEVXJXWHQDUXN.pause_animation()\"><img class=\"anim_icon\" src=\"\"></button>\n",
       "    <button onclick=\"animICDEEVXJXWHQDUXN.play_animation()\"><img class=\"anim_icon\" src=\"\"></button>\n",
       "    <button onclick=\"animICDEEVXJXWHQDUXN.next_frame()\"><img class=\"anim_icon\" src=\"\"></button>\n",
       "    <button onclick=\"animICDEEVXJXWHQDUXN.last_frame()\"><img class=\"anim_icon\" src=\"\"></button>\n",
       "    <button onclick=\"animICDEEVXJXWHQDUXN.faster()\">+</button>\n",
       "  <form action=\"#n\" name=\"_anim_loop_selectICDEEVXJXWHQDUXN\" class=\"anim_control\">\n",
       "    <input type=\"radio\" name=\"state\" value=\"once\" > Once </input>\n",
       "    <input type=\"radio\" name=\"state\" value=\"loop\" checked> Loop </input>\n",
       "    <input type=\"radio\" name=\"state\" value=\"reflect\" > Reflect </input>\n",
       "  </form>\n",
       "</div>\n",
       "\n",
       "\n",
       "<script language=\"javascript\">\n",
       "  /* Instantiate the Animation class. */\n",
       "  /* The IDs given should match those used in the template above. */\n",
       "  (function() {\n",
       "    var img_id = \"_anim_imgICDEEVXJXWHQDUXN\";\n",
       "    var slider_id = \"_anim_sliderICDEEVXJXWHQDUXN\";\n",
       "    var loop_select_id = \"_anim_loop_selectICDEEVXJXWHQDUXN\";\n",
       "    var frames = new Array(0);\n",
       "    \n",
       "  frames[0] = \"\"\n",
       "  frames[1] = \"\"\n",
       "  frames[2] = \"\"\n",
       "  frames[3] = \"\"\n",
       "  frames[4] = \"\"\n",
       "  frames[5] = \"\"\n",
       "  frames[6] = \"\"\n",
       "  frames[7] = \"\"\n",
       "  frames[8] = \"\"\n",
       "  frames[9] = \"\"\n",
       "  frames[10] = \"\"\n",
       "  frames[11] = \"\"\n",
       "  frames[12] = \"\"\n",
       "  frames[13] = \"\"\n",
       "  frames[14] = \"\"\n",
       "  frames[15] = \"\"\n",
       "  frames[16] = \"\"\n",
       "  frames[17] = \"\"\n",
       "  frames[18] = \"\"\n",
       "  frames[19] = \"\"\n",
       "  frames[20] = \"\"\n",
       "  frames[21] = \"\"\n",
       "  frames[22] = \"\"\n",
       "  frames[23] = \"\"\n",
       "  frames[24] = \"\"\n",
       "  frames[25] = \"\"\n",
       "  frames[26] = \"\"\n",
       "  frames[27] = \"\"\n",
       "  frames[28] = \"\"\n",
       "  frames[29] = \"\"\n",
       "  frames[30] = \"\"\n",
       "  frames[31] = \"\"\n",
       "  frames[32] = \"\"\n",
       "  frames[33] = \"\"\n",
       "  frames[34] = \"\"\n",
       "  frames[35] = \"\"\n",
       "  frames[36] = \"\"\n",
       "  frames[37] = \"\"\n",
       "  frames[38] = \"\"\n",
       "  frames[39] = \"\"\n",
       "  frames[40] = \"\"\n",
       "  frames[41] = \"\"\n",
       "  frames[42] = \"\"\n",
       "  frames[43] = \"\"\n",
       "  frames[44] = \"\"\n",
       "  frames[45] = \"\"\n",
       "  frames[46] = \"\"\n",
       "  frames[47] = \"\"\n",
       "  frames[48] = \"\"\n",
       "  frames[49] = \"\"\n",
       "  frames[50] = \"\"\n",
       "  frames[51] = \"\"\n",
       "  frames[52] = \"\"\n",
       "  frames[53] = \"\"\n",
       "  frames[54] = \"\"\n",
       "  frames[55] = \"\"\n",
       "  frames[56] = \"\"\n",
       "  frames[57] = \"\"\n",
       "  frames[58] = \"\"\n",
       "  frames[59] = \"\"\n",
       "  frames[60] = \"\"\n",
       "  frames[61] = \"\"\n",
       "  frames[62] = \"\"\n",
       "  frames[63] = \"\"\n",
       "  frames[64] = \"\"\n",
       "  frames[65] = \"\"\n",
       "  frames[66] = \"\"\n",
       "  frames[67] = \"\"\n",
       "  frames[68] = \"\"\n",
       "  frames[69] = \"\"\n",
       "  frames[70] = \"\"\n",
       "  frames[71] = \"\"\n",
       "  frames[72] = \"\"\n",
       "  frames[73] = \"\"\n",
       "  frames[74] = \"\"\n",
       "  frames[75] = \"\"\n",
       "  frames[76] = \"\"\n",
       "  frames[77] = \"\"\n",
       "  frames[78] = \"\"\n",
       "  frames[79] = \"\"\n",
       "  frames[80] = \"\"\n",
       "  frames[81] = \"\"\n",
       "  frames[82] = \"\"\n",
       "  frames[83] = \"\"\n",
       "  frames[84] = \"\"\n",
       "  frames[85] = \"\"\n",
       "  frames[86] = \"\"\n",
       "  frames[87] = \"\"\n",
       "  frames[88] = \"\"\n",
       "  frames[89] = \"\"\n",
       "  frames[90] = \"\"\n",
       "  frames[91] = \"\"\n",
       "  frames[92] = \"\"\n",
       "  frames[93] = \"\"\n",
       "  frames[94] = \"\"\n",
       "  frames[95] = \"\"\n",
       "  frames[96] = \"\"\n",
       "  frames[97] = \"\"\n",
       "  frames[98] = \"\"\n",
       "  frames[99] = \"\"\n",
       "  frames[100] = \"\"\n",
       "  frames[101] = \"\"\n",
       "  frames[102] = \"\"\n",
       "  frames[103] = \"\"\n",
       "  frames[104] = \"\"\n",
       "  frames[105] = \"\"\n",
       "  frames[106] = \"\"\n",
       "  frames[107] = \"\"\n",
       "  frames[108] = \"\"\n",
       "  frames[109] = \"\"\n",
       "  frames[110] = \"\"\n",
       "  frames[111] = \"\"\n",
       "  frames[112] = \"\"\n",
       "  frames[113] = \"\"\n",
       "  frames[114] = \"\"\n",
       "  frames[115] = \"\"\n",
       "  frames[116] = \"\"\n",
       "  frames[117] = \"\"\n",
       "  frames[118] = \"\"\n",
       "  frames[119] = \"\"\n",
       "  frames[120] = \"\"\n",
       "  frames[121] = \"\"\n",
       "  frames[122] = \"\"\n",
       "  frames[123] = \"\"\n",
       "  frames[124] = \"\"\n",
       "  frames[125] = \"\"\n",
       "  frames[126] = \"\"\n",
       "  frames[127] = \"\"\n",
       "  frames[128] = \"\"\n",
       "  frames[129] = \"\"\n",
       "  frames[130] = \"\"\n",
       "  frames[131] = \"\"\n",
       "  frames[132] = \"\"\n",
       "  frames[133] = \"\"\n",
       "  frames[134] = \"\"\n",
       "  frames[135] = \"\"\n",
       "  frames[136] = \"\"\n",
       "  frames[137] = \"\"\n",
       "  frames[138] = \"\"\n",
       "  frames[139] = \"\"\n",
       "  frames[140] = \"\"\n",
       "  frames[141] = \"\"\n",
       "  frames[142] = \"\"\n",
       "  frames[143] = \"\"\n",
       "  frames[144] = \"\"\n",
       "  frames[145] = \"\"\n",
       "  frames[146] = \"\"\n",
       "  frames[147] = \"\"\n",
       "  frames[148] = \"\"\n",
       "  frames[149] = \"\"\n",
       "  frames[150] = \"\"\n",
       "  frames[151] = \"\"\n",
       "  frames[152] = \"\"\n",
       "  frames[153] = \"\"\n",
       "  frames[154] = \"\"\n",
       "  frames[155] = \"\"\n",
       "  frames[156] = \"\"\n",
       "  frames[157] = \"\"\n",
       "  frames[158] = \"\"\n",
       "  frames[159] = \"\"\n",
       "  frames[160] = \"\"\n",
       "  frames[161] = \"\"\n",
       "  frames[162] = \"\"\n",
       "  frames[163] = \"\"\n",
       "  frames[164] = \"\"\n",
       "  frames[165] = \"\"\n",
       "  frames[166] = \"\"\n",
       "  frames[167] = \"\"\n",
       "  frames[168] = \"\"\n",
       "  frames[169] = \"\"\n",
       "  frames[170] = \"\"\n",
       "  frames[171] = \"\"\n",
       "  frames[172] = \"\"\n",
       "  frames[173] = \"\"\n",
       "  frames[174] = \"\"\n",
       "  frames[175] = \"\"\n",
       "  frames[176] = \"\"\n",
       "  frames[177] = \"\"\n",
       "  frames[178] = \"\"\n",
       "  frames[179] = \"\"\n",
       "  frames[180] = \"\"\n",
       "  frames[181] = \"\"\n",
       "  frames[182] = \"\"\n",
       "  frames[183] = \"\"\n",
       "  frames[184] = \"\"\n",
       "  frames[185] = \"\"\n",
       "  frames[186] = \"\"\n",
       "  frames[187] = \"\"\n",
       "  frames[188] = \"\"\n",
       "  frames[189] = \"\"\n",
       "  frames[190] = \"\"\n",
       "  frames[191] = \"\"\n",
       "  frames[192] = \"\"\n",
       "  frames[193] = \"\"\n",
       "  frames[194] = \"\"\n",
       "  frames[195] = \"\"\n",
       "  frames[196] = \"\"\n",
       "  frames[197] = \"\"\n",
       "  frames[198] = \"\"\n",
       "  frames[199] = \"\"\n",
       "\n",
       "\n",
       "    /* set a timeout to make sure all the above elements are created before\n",
       "       the object is initialized. */\n",
       "    setTimeout(function() {\n",
       "        animICDEEVXJXWHQDUXN = new Animation(frames, img_id, slider_id, 50, loop_select_id);\n",
       "    }, 0);\n",
       "  })()\n",
       "</script>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Imports specifically so we can render outputs in Colab.\n",
    "from matplotlib import animation\n",
    "from JSAnimation.IPython_display import display_animation\n",
    "from IPython.display import display\n",
    "\n",
    "\n",
    "def display_frames_as_gif(frames):\n",
    "    \"\"\"Displays a list of frames as a gif, with controls.\"\"\"\n",
    "    patch = plt.imshow(frames[0])\n",
    "    plt.axis('off')\n",
    "\n",
    "    def animate(i):\n",
    "        patch.set_data(frames[i])\n",
    "\n",
    "    anim = animation.FuncAnimation(\n",
    "        plt.gcf(), animate, frames = len(frames), interval=50\n",
    "    )\n",
    "    display(display_animation(anim, default_mode='loop'))\n",
    "    \n",
    "        \n",
    "# display \n",
    "display_frames_as_gif(frames)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
